﻿Namespace Localization
    <System.Windows.Markup.ContentProperty("Items")>
    Public Class LocalizedList
        Implements IValueConverter, IEnumerable


        Public Sub New()
            MyBase.New()
        End Sub

        Private _Items As LocalizedEntryCollection
        Public ReadOnly Property Items As LocalizedEntryCollection
            Get
                If _Items Is Nothing Then
                    _Items = New LocalizedEntryCollection(Me)
                End If
                Return _Items
            End Get
        End Property

        Private _IsToStringOnly As Boolean = False
        Public Property IsToStringOnly() As Boolean
            Get
                Return _IsToStringOnly
            End Get
            Set(ByVal value As Boolean)
                _IsToStringOnly = value
            End Set
        End Property

        Private _ValueEnum As Type
        Public Property ValueEnum() As Type
            Get
                Return _ValueEnum
            End Get
            Set(ByVal value As Type)
                If value Is Nothing Then
                    _ValueEnum = value
                    _IsFlags = False
                ElseIf value.IsEnum Then
                    _ValueEnum = value
                    _IsFlags = (_ValueEnum.GetCustomAttributes(GetType(System.FlagsAttribute), True).Length > 0)
                Else
                    Throw New ArgumentException("ValueEnum must be an Enum type.")
                End If
                _Items = Nothing
            End Set
        End Property

        Private _Definition As String
        Public Property Definition() As String
            Get
                Return _Definition
            End Get
            Set(ByVal value As String)
                _Definition = value
                _Items = Nothing
            End Set
        End Property

        Private _ListSeparator As Char = ","c
        Public Property ListSeparator() As Char
            Get
                Return _ListSeparator
            End Get
            Set(ByVal value As Char)
                _ListSeparator = value
            End Set
        End Property

        Private _ValueSeparator As Char = "="c
        Public Property ValueSeparator() As Char
            Get
                Return _ValueSeparator
            End Get
            Set(ByVal value As Char)
                _ValueSeparator = value
            End Set
        End Property

        Public Property CombinationChar As Char = "+"c

        Private _IsFlags As Boolean = False
        Public ReadOnly Property IsFlags() As Boolean
            Get
                Return _IsFlags
            End Get
        End Property


        Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
            Dim result As String = Nothing
            If IsFlags Then
                If CInt(value) = 0 Then
                    result = Items.GetKeyForValue(0)
                Else
                    For Each enumValue As Object In [Enum].GetValues(_ValueEnum)
                        If (value And enumValue) <> 0 Then
                            Dim decodedvalue As String = Items.GetKeyForValue(enumValue)
                            If decodedvalue IsNot Nothing Then
                                If result Is Nothing Then
                                    result = decodedvalue
                                Else
                                    result = result & CombinationChar & decodedvalue
                                End If
                            End If
                        End If
                    Next
                End If
            Else
                result = Items.GetKeyForValue(value)
            End If
            If result Is Nothing Then
                Return Binding.DoNothing
            Else
                Return result
            End If
        End Function

        Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
            Dim result As Object = Nothing
            If IsToStringOnly Then
                Throw New NotSupportedException("Set IsToStringOnly to false in order to support twoway conversions")
            End If
            If IsFlags Then
                Dim strings() As String = value.ToString().Split(CombinationChar)
                For Each s As String In strings
                    Dim decodedValue As Object = Items.GetValueForKey(s.Trim)
                    If decodedValue IsNot Nothing Then
                        If result Is Binding.DoNothing Then
                            result = CInt(decodedValue)
                        Else
                            result = CInt(result) Or CInt(decodedValue)
                        End If
                    End If
                Next
            Else
                result = Items.GetValueForKey(value.ToString)
            End If
            If result Is Nothing Then
                Return Binding.DoNothing
            Else
                Return result
            End If
        End Function

        Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
            Return DirectCast(Items, IList).GetEnumerator
        End Function
    End Class

    Public Class Entry

        Public Sub New()
            MyClass.New(Nothing, Nothing)
        End Sub

        Public Sub New(ByVal key As String, ByVal value As Object)
            MyBase.New()
            _Key = key
            _Value = value
        End Sub

        Private _Key As String
        Public Property Key() As String
            Get
                Return _Key
            End Get
            Set(ByVal value As String)
                _Key = value
            End Set
        End Property

        Private _Value As Object
        Public Property Value() As Object
            Get
                Return _Value
            End Get
            Set(ByVal value As Object)
                _Value = value
            End Set
        End Property

        Public Overrides Function Equals(ByVal obj As Object) As Boolean
            If TypeOf obj Is Entry Then
                Return (Key = DirectCast(obj, Entry).Key)
            End If
            If TypeOf obj Is String Then
                Return (Key = CStr(obj))
            End If
            Return MyBase.Equals(obj)
        End Function

        Public Overrides Function ToString() As String
            Return _Key
        End Function
    End Class

    Public Class LocalizedEntryCollection
        Inherits System.Collections.ObjectModel.Collection(Of Entry)
        Private _ToString As New Dictionary(Of Object, Entry)
        Private _ToValue As New Dictionary(Of String, Entry)
        Private _NullKeyEntry As Entry
        Private _NullValueEntry As Entry
        Private _Parent As LocalizedList

        Friend Sub New(ByVal parent As LocalizedList)
            MyBase.New()
            Initialize(parent)
        End Sub

        Private Function AddDictionaryEntries(ByVal entry As Entry) As Boolean
            If entry IsNot Nothing Then
                With entry
                    If .Key Is Nothing And .Value IsNot Nothing Then
                        _NullKeyEntry = entry
                        Return True
                    ElseIf .Key IsNot Nothing And .Value Is Nothing Then
                        _NullValueEntry = entry
                        Return True
                    ElseIf .Key IsNot Nothing And .Value IsNot Nothing Then
                        If Not _Parent.IsToStringOnly Then
                            _ToValue.Add(.Key, entry)
                        End If
                        _ToString.Add(.Value, entry)
                        Return True
                    End If
                End With
            End If
            Return False
        End Function

        Private Sub RemoveDictionaryEntries(ByVal entry As Entry)
            If entry IsNot Nothing Then
                With entry
                    If .Key Is Nothing And .Value IsNot Nothing Then
                        _NullKeyEntry = Nothing
                    ElseIf .Key IsNot Nothing And .Value Is Nothing Then
                        _NullValueEntry = Nothing
                    ElseIf .Key IsNot Nothing And .Value IsNot Nothing Then
                        If Not _Parent.IsToStringOnly Then
                            _ToValue.Remove(.Key)
                        End If
                        _ToString.Remove(.Value)
                    End If
                End With
            End If
        End Sub

        Public Function GetValueForKey(ByVal key As String) As Object
            Dim result As Object = Nothing
            If Not _Parent.IsToStringOnly Then
                If key Is Nothing AndAlso _NullKeyEntry IsNot Nothing Then
                    result = _NullKeyEntry.Value
                ElseIf _ToValue.ContainsKey(key) Then
                    result = _ToValue(key).Value
                End If
            End If
            Return result
        End Function

        Public Function GetKeyForValue(ByVal value As Object) As String
            Dim result As String = Nothing
            If value Is Nothing And _NullValueEntry IsNot Nothing Then
                result = _NullValueEntry.Key
            ElseIf _ToString.ContainsKey(value) Then
                result = _ToString(value).Key
            End If
            Return result
        End Function

        Private Sub Initialize(ByVal parent As LocalizedList)
            _Parent = parent
            If parent IsNot Nothing Then
                With _Parent
                    If .Definition Is Nothing Then
                        If .ValueEnum IsNot Nothing Then
                            For Each v As Object In [Enum].GetValues(.ValueEnum)
                                Dim s As String = [Enum].GetName(.ValueEnum, v)
                                Add(New Entry(s, v))
                            Next
                        End If
                    Else
                        Dim items() As String = .Definition.Split(New Char() {.ListSeparator}, StringSplitOptions.None)
                        For Each def As String In items
                            Dim values() As String = def.Split(New Char() {.ValueSeparator}, 2, StringSplitOptions.None)
                            If values.Length = 1 Then
                                Dim value As Object = ParseValue(values(0))
                                Add(New Entry(values(0), value))
                            ElseIf values.Length = 2 Then
                                Dim value As Object = ParseValue(values(0))
                                Add(New Entry(values(1), value))
                            End If
                        Next
                    End If
                End With
            End If
        End Sub

        Private Function ParseValue(ByVal value As String) As Object
            Dim result As Object = value
            If _Parent IsNot Nothing Then
                With _Parent
                    If .ValueEnum IsNot Nothing AndAlso [Enum].GetNames(.ValueEnum).Contains(value) Then
                        result = [Enum].Parse(.ValueEnum, value.Trim())
                    End If
                End With
            End If
            Return result
        End Function

        Protected Overrides Sub ClearItems()
            MyBase.ClearItems()
            _ToString.Clear()
            _ToValue.Clear()
            _NullKeyEntry = Nothing
            _NullValueEntry = Nothing
        End Sub

        Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As Entry)
            If AddDictionaryEntries(item) Then
                MyBase.InsertItem(index, item)
            End If
        End Sub

        Protected Overrides Sub RemoveItem(ByVal index As Integer)
            RemoveDictionaryEntries(Me.Item(index))
            MyBase.RemoveItem(index)
        End Sub

        Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As Entry)
            RemoveDictionaryEntries(Me.Item(index))
            If AddDictionaryEntries(item) Then
                MyBase.SetItem(index, item)
            End If
        End Sub
    End Class
End Namespace
